/*
 * Copyright (C) 2011 Alex Kuiper
 * 
 * This file is part of PageTurner
 *
 * PageTurner is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * PageTurner is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with PageTurner.  If not, see <http://www.gnu.org/licenses/>.*
 */

package net.nightwhistler.pageturner.view.bookview;

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RectShape;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.os.Build;
import android.os.Handler;
import android.text.Layout;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.SpannedString;
import android.text.TextUtils;
import android.text.style.BackgroundColorSpan;
import android.text.style.ClickableSpan;
import android.text.style.ImageSpan;
import android.util.AttributeSet;
import android.util.Base64;
import android.util.Log;
import android.view.ActionMode;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.google.inject.Inject;
import com.google.inject.Provider;
import com.hzdracom.epubreader.activity.PictureActivity;
import com.hzdracom.epubreader.consts.EpubConst;
import com.hzdracom.epubreader.util.FileUtil;
import com.shwread.android.qysw10000243.R;

import net.nightwhistler.htmlspanner.FontFamily;
import net.nightwhistler.htmlspanner.HtmlSpanner;
import net.nightwhistler.htmlspanner.SpanStack;
import net.nightwhistler.htmlspanner.TagNodeHandler;
import net.nightwhistler.htmlspanner.handlers.TableHandler;
import net.nightwhistler.htmlspanner.spans.CenterSpan;
import net.nightwhistler.htmlspanner.tyydextension.ChapterSpan;
import net.nightwhistler.pageturner.Configuration;
import net.nightwhistler.pageturner.activity.VideoPlayActivity;
import net.nightwhistler.pageturner.dto.HighLight;
import net.nightwhistler.pageturner.dto.SearchResult;
import net.nightwhistler.pageturner.dto.TocEntry;
import net.nightwhistler.pageturner.epub.PageTurnerSpine;
import net.nightwhistler.pageturner.epub.ResourceLoader;
import net.nightwhistler.pageturner.epub.ResourceLoader.ResourceCallback;
import net.nightwhistler.pageturner.scheduling.QueueableAsyncTask;
import net.nightwhistler.pageturner.scheduling.TaskQueue;
import net.nightwhistler.pageturner.view.FastBitmapDrawable;
import net.nightwhistler.pageturner.view.bookview.TextLoader.AsideTagHandler;
import net.nightwhistler.pageturner.view.bookview.TextLoader.AudioTagHandler;

import org.htmlcleaner.TagNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import nl.siegmann.epublib.Constants;
import nl.siegmann.epublib.domain.Book;
import nl.siegmann.epublib.domain.Resource;
import nl.siegmann.epublib.domain.TOCReference;
import nl.siegmann.epublib.util.IOUtil;
import nl.siegmann.epublib.util.StringUtil;
import roboguice.RoboGuice;

public class BookView extends FrameLayout implements LinkTagHandler.LinkCallBack {

    public static final String COMPLEX_OBJ_STRING_HOLDER = "\uFFFC";

    public static BookView instance;

    private int storedIndex;
    private String storedAnchor;
    private InnerView childView;

    private CoverChapterView coverChapterView;

    private Set<BookViewListener> listeners;

    private TableHandler tableHandler;

    private PageTurnerSpine spine;

    private long bookId;
    private String fileName;
    private Book book;

    private int prevIndex = -1;
    private int prevPos = -1;

    private PageChangeStrategy strategy;
    private ResourceLoader loader;

    private int horizontalMargin = 0;
    private int verticalMargin = 0;
    private int lineSpacing = 0;

    private Handler scrollHandler;

    private static final Logger LOG = LoggerFactory.getLogger("BookView");

    @Inject
    private Configuration configuration;

    @Inject
    private TextLoader textLoader;

    @Inject
    private EpubFontResolver fontResolver;

    @Inject
    private TaskQueue taskQueue;

    @Inject
    private Provider<FixedPagesStrategy> fixedPagesStrategyProvider;

    // @Inject
    // private Provider<ScrollingStrategy> scrollingStrategyProvider;

    private OnTouchListener onTouchListener;

    public BookView(Context context, AttributeSet attributes) {
        super(context, attributes);
        this.scrollHandler = new Handler();
        RoboGuice.injectMembers(context, this);
        instance = this;
    }

    public void setBookId(long bookId) {
        this.bookId = bookId;
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public void init() {
        this.listeners = new HashSet<>();
        this.childView = (InnerView) this.findViewById(R.id.innerView);
        this.childView.setBookView(this);

        childView.setCursorVisible(false);
        childView.setLongClickable(true);
        this.setVerticalFadingEdgeEnabled(false);
        childView.setFocusable(true);
        childView.setLinksClickable(true);

        this.setVerticalScrollBarEnabled(false);

        if (Build.VERSION.SDK_INT >= Configuration.TEXT_SELECTION_PLATFORM_VERSION) {
            childView.setTextIsSelectable(true);
        }

        // this.setSmoothScrollingEnabled(false);
        this.tableHandler = new TableHandler();
        this.textLoader.registerTagNodeHandler("table", tableHandler);

        ImageTagHandler imgHandler = new ImageTagHandler(false);
        this.textLoader.registerTagNodeHandler("img", imgHandler);
        this.textLoader.registerTagNodeHandler("image", imgHandler);

        VideoAudioTagHandler videoHandler = new VideoAudioTagHandler(false);
        this.textLoader.registerTagNodeHandler("source", videoHandler);

        SmallTagHandler smallHandler = new SmallTagHandler(false);
        this.textLoader.registerTagNodeHandler("small", smallHandler);

        AudioTagHandler audioHandler = textLoader.new AudioTagHandler(false);
        this.textLoader.registerTagNodeHandler("audio", audioHandler);

        AsideTagHandler asideHandler = textLoader.new AsideTagHandler(false);
        this.textLoader.registerTagNodeHandler("aside", asideHandler);

        this.textLoader.setLinkCallBack(this);
    }

    private void onInnerViewResize() {
        restorePosition();

        if (this.tableHandler != null) {
            int tableWidth = (int) (childView.getWidth() * 0.9);
            tableHandler.setTableWidth(tableWidth);
        }
    }

    /**
     * 设置章节封面View的引用
     */
    public void setCoverChapterView(CoverChapterView coverChapterView) {
        this.coverChapterView = coverChapterView;
    }

    public String getFileName() {
        return fileName;
    }

    @Override
    public void linkClicked(String href) {
        navigateTo(spine.resolveHref(href));
    }

    public PageChangeStrategy getStrategy() {
        return this.strategy;
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        progressUpdate();
    }

    /**
     * Returns if we're at the start of the book, i.e. displaying the title
     * page.
     */
    public boolean isAtStart() {
        return spine == null || spine.getPosition() == 0 && strategy.isAtStart();
    }

    public boolean isAtEnd() {
        return spine != null && spine.getPosition() >= spine.size() - 1 && strategy.isAtEnd();
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
        this.loader = new ResourceLoader(fileName);
    }

    @Override
    public void setOnTouchListener(OnTouchListener l) {
        this.onTouchListener = l;
        super.setOnTouchListener(l);
        this.childView.setOnTouchListener(l);
    }

    public HighlightSpan[] getHighlightsAt(float x, float y) {
        return getSpansAt(x, y, HighlightSpan.class);
    }

    public ClickableSpan[] getLinkAt(float x, float y) {
        return getSpansAt(x, y, ClickableSpan.class);
    }

    public ImageSpan[] getImageSpan(float x, float y) {
        return getSpansAt(x, y, ImageSpan.class);
    }

    public VideoImageSpan[] getVideoImageSpan(float x, float y) {
        return getSpansAt(x, y, VideoImageSpan.class);
    }

    private <A> A[] getSpansAt(float x, float y, Class<A> spanClass) {
        Integer offset = findOffsetForPosition(x, y);

        CharSequence text = childView.getText();

        if (offset == null || !(text instanceof Spanned)) {
            return null;
        }
        Spanned spannedText = (Spanned) text;
        return spannedText.getSpans(offset, offset, spanClass);
    }

    /**
     * Blocks the inner-view from creating action-modes for a given amount of
     * time.
     */
    public void blockFor(long time) {
        this.childView.setBlockUntil(System.currentTimeMillis() + time);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        if (strategy.isScrolling()) {
            this.setVerticalScrollBarEnabled(true);
            return super.onTouchEvent(ev);
        } else {
            this.setVerticalScrollBarEnabled(false);
            return childView.onTouchEvent(ev);
        }
    }

    /**
     * 是否有上一次的位置信息
     */
    public boolean hasPrevPosition() {
        return this.prevIndex != -1 && this.prevPos != -1;
    }

    /**
     * 设置行间距
     */
    public void setLineSpacing(int lineSpacing) {
        if (lineSpacing != this.lineSpacing) {
            this.lineSpacing = lineSpacing;
            this.childView.setLineSpacing(lineSpacing, 1);
            if (strategy != null) {
                strategy.updatePosition();
            }
        }
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public void setTextSelectionCallback(TextSelectionCallback callback) {
        if (Build.VERSION.SDK_INT >= Configuration.TEXT_SELECTION_PLATFORM_VERSION) {
            this.childView.setCustomSelectionActionModeCallback(new TextSelectionActions(getContext(), callback, this));
        }
    }

    public int getLineSpacing() {
        return lineSpacing;
    }

    public void setHorizontalMargin(int horizontalMargin) {

        if (horizontalMargin != this.horizontalMargin) {
            this.horizontalMargin = horizontalMargin;

            setPadding(getPxByDp(this.horizontalMargin), getPxByDp(this.verticalMargin),
                    getPxByDp(this.horizontalMargin), getPxByDp(this.verticalMargin));
            if (strategy != null) {
                strategy.updatePosition();
            }
        }
    }

    public int getPxByDp(int dpValue) {
        final float scale = getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * 释放当前资源，打开新书的时候调用
     */
    public void releaseResources() {
        this.strategy.clearText();
        this.textLoader.closeCurrentBook();
        this.taskQueue.clear();
        this.spine = null;
    }

    /**
     * 设置链接字体颜色
     */
    public void setLinkColor(int color) {
        this.childView.setLinkTextColor(color);
    }

    public void setVerticalMargin(int verticalMargin) {
        if (verticalMargin != this.verticalMargin) {
            this.verticalMargin = verticalMargin;
            setPadding(getPxByDp(this.horizontalMargin), getPxByDp(this.verticalMargin),
                    getPxByDp(this.horizontalMargin), getPxByDp(this.verticalMargin));
            if (strategy != null) {
                strategy.updatePosition();
            }
        }
    }

    public int getHorizontalMargin() {
        return horizontalMargin;
    }

    public int getVerticalMargin() {
        return verticalMargin;
    }

    public int getSelectionStart() {
        return childView.getSelectionStart();
    }

    public int getSelectionEnd() {
        return childView.getSelectionEnd();
    }

    public String getSelectedText() {
        return childView.getText().subSequence(getSelectionStart(), getSelectionEnd()).toString();
    }

    public void goBackInHistory() {
        if (this.prevIndex == this.getIndex()) {
            strategy.setPosition(prevPos);

            storedAnchor = null;
            prevIndex = -1;
            prevPos = -1;

            restorePosition();
        } else {
            this.strategy.clearText();
            this.spine.navigateByIndex(this.prevIndex);
            strategy.setPosition(this.prevPos);

            this.storedAnchor = null;
            this.prevIndex = -1;
            this.prevPos = -1;

            loadText();
        }
    }

    /**
     * 清空当前View中的文字和状态
     */
    public void clear() {
        this.childView.setText("");
        this.storedAnchor = null;
        this.storedIndex = -1;
        this.book = null;
        this.fileName = null;

        this.strategy.reset();
    }

    public void clearPageOffsets() {
        if (this.spine != null) {
            this.spine.setPageOffsets(null);
        }
    }

    /**
     * Loads the text and saves the restored position.
     */
    public void restore() {
        strategy.clearText();
        loadText();
    }

    public void invalidateCachedText() {
        this.textLoader.invalidateCachedText();
    }

    public void setIndex(int index) {
        this.storedIndex = index;
    }

    void loadText() {

        if (spine == null && !textLoader.hasCachedBook(this.fileName)) {
            taskQueue.executeTask(new OpenFileTask());
            taskQueue.executeTask(new LoadTextTask());
            taskQueue.executeTask(new PreLoadTask());
            taskQueue.executeTask(new CalculatePageNumbersTask());
        } else {

            if (spine == null) {
                try {
                    Book book = initBookAndSpine();

                    if (book != null) {
                        bookOpened(book);
                    }

                } catch (IOException io) {
                    errorOnBookOpening(io.getMessage());
                    return;
                } catch (OutOfMemoryError e) {

                    errorOnBookOpening(getContext().getString(R.string.out_of_memory));
                    return;
                }
            }

            loadText(spine.getCurrentResource());
        }
    }

    private void loadText(Resource resource) {

        LOG.debug("Trying to load text for resource " + resource);

        ChapterSpan cachedText = textLoader.getCachedTextForResource(resource);

        // Start by clearing the queue
        taskQueue.clear();

        if (cachedText != null && getInnerView().getWidth() > 0) {

            LOG.debug("Text is cached, loading on UI Thread.");
            loadText(cachedText.getSpannable());
            setCoverChapterView(cachedText);

            for (BookViewListener listener : this.listeners) {
                listener.chapterUpdate(this.spine.getPosition(), cachedText.getCoverPageInfo().getChapterTitle());
            }

        } else {

            LOG.debug("Text is NOT cached, loading in background.");
            taskQueue.executeTask(new LoadTextTask(), resource);
        }

        taskQueue.executeTask(new PreLoadTask());

        if (needsPageNumberCalculation()) {
            this.spine.setPageOffsets(null);
            taskQueue.executeTask(new CalculatePageNumbersTask());
        }
    }

    /**
     * 设置封面页
     */
    private void setCoverChapterView(ChapterSpan cachedText) {
        if (cachedText.getCoverPageInfo().isHaveData()) {
            // 本资源是封面页，设置封面页
            coverChapterView.setVisibility(View.VISIBLE);
            coverChapterView.UpdateUI(cachedText.getCoverPageInfo().getTitle(), cachedText.getCoverPageInfo()
                    .getSubTitle());

            coverChapterView.SetTextType_Color(configuration.getTextColor(), configuration.getDefaultFontFamily());

            // 设置背景颜色和背景图片
            coverChapterView.setBackgroundColor(configuration.getBackgroundColor());
            // 背景颜色
            if (cachedText.getCoverPageInfo().getBackgroundColor() != null
                    && !cachedText.getCoverPageInfo().getBackgroundColor().equals("")
                    && cachedText.getCoverPageInfo().getBackgroundColor().length() == 4) {
                // 设置颜色
                String color = cachedText.getCoverPageInfo().getBackgroundColor().substring(1);
                String HexR = color.substring(0, 1);
                String HexG = color.substring(1, 2);
                String HexB = color.substring(2, 3);
                int r = Integer.valueOf(HexR + HexR, 16);
                int g = Integer.valueOf(HexG + HexG, 16);
                int b = Integer.valueOf(HexB + HexB, 16);
                // 设置背景
                coverChapterView.setBackgroundColor(Color.rgb(r, g, b));
            }

            // 设置背景图片
            if (cachedText.getCoverPageInfo().getBackgroundImage() != null
                    && !cachedText.getCoverPageInfo().getBackgroundImage().equals("")) {
                String resolvedHref = spine.resolveHref(cachedText.getCoverPageInfo().getBackgroundImage());
                Resource imageResource = book.getResources().getByHref(resolvedHref);
                if (imageResource != null) {
                    try {
                        InputStream is = imageResource.getInputStream();
                        FastBitmapDrawable cover = new FastBitmapDrawable(BitmapFactory.decodeStream(is));
                        coverChapterView.setCoverImage(cover);

                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

        } else {
            coverChapterView.setVisibility(View.GONE);
            coverChapterView.clear();
        }
    }

    private void loadText(Spanned text) {

        strategy.loadText(text);

        restorePosition();
        strategy.updateGUI();
        progressUpdate();
        parseEntryComplete(spine.getCurrentTitle());
    }

    private void loadText(String searchTerm) {
        this.taskQueue.jumpQueueExecuteTask(new LoadTextTask(searchTerm), spine.getCurrentResource());
    }

    /**
     * 初始化epubBook和Spine
     *
     * @throws IOException
     */
    private Book initBookAndSpine() throws IOException {

        Book book = textLoader.initBook(fileName);

        this.book = book;
        this.spine = new PageTurnerSpine(book);

        this.spine.navigateByIndex(BookView.this.storedIndex);

        // 获取之前的分页计算信息
        // if (configuration.isShowPageNumbers()) {

        List<List<Integer>> offsets = configuration.getPageOffsets(fileName);

        if (offsets != null && offsets.size() > 0) {
            spine.setPageOffsets(offsets);
        }
        // }
        return book;

    }

    /**
     * 设置字体
     */
    public void setFontFamily(FontFamily family) {
        this.childView.setTypeface(family.getDefaultTypeface());
        this.tableHandler.setTypeFace(family.getDefaultTypeface());
    }

    public void pageDown() {
        strategy.pageDown();
        progressUpdate();
        stopAudio();
    }

    public void pageUp() {
        strategy.pageUp();
        progressUpdate();
        stopAudio();
    }

    /**
     * 下一章
     */
    public void turnNextChapter() {
        if (spine == null || !spine.navigateForward()) {
            return;
        }
        strategy.clearText();
        strategy.clearStoredPosition();
        loadText();
    }

    /**
     * 上一章
     */
    public void turnPrevChapter() {
        if (spine == null || !spine.navigateBack()) {
            return;
        }
        strategy.clearText();
        strategy.clearStoredPosition();
        loadText();
    }

    TextView getInnerView() {
        return childView;
    }

    public PageTurnerSpine getSpine() {
        return this.spine;
    }

    private Integer findOffsetForPosition(float x, float y) {

        if (childView == null || childView.getLayout() == null) {
            return null;
        }

        Layout layout = this.childView.getLayout();
        int line = layout.getLineForVertical((int) y);

        return layout.getOffsetForHorizontal(line, x);
    }

    /**
     * Returns the full word containing the character at the selected location.
     *
     * @param x x
     * @param y y
     * @return SelectedWord
     */
    public SelectedWord getWordAt(float x, float y) {

        if (childView == null) {
            return null;
        }

        CharSequence text = this.childView.getText();

        if (text.length() == 0) {
            return null;
        }

        Integer offset = findOffsetForPosition(x, y);

        if (offset == null) {
            return null;
        }

        if (offset < 0 || offset > text.length() - 1) {
            return null;
        }

        if (isBoundaryCharacter(text.charAt(offset))) {
            return null;
        }

        int left = Math.max(0, offset - 1);
        int right = Math.min(text.length(), offset);

        CharSequence word = text.subSequence(left, right);
        while (left > 0 && !isBoundaryCharacter(word.charAt(0))) {
            left--;
            word = text.subSequence(left, right);
        }

        if (word.length() == 0) {
            return null;
        }

        while (right < text.length() && !isBoundaryCharacter(word.charAt(word.length() - 1))) {
            right++;
            word = text.subSequence(left, right);
        }

        int start = 0;
        int end = word.length();

        if (isBoundaryCharacter(word.charAt(0))) {
            start = 1;
            left++;
        }

        if (isBoundaryCharacter(word.charAt(word.length() - 1))) {
            end = word.length() - 1;
            right--;
        }

        if (start > 0 && start < word.length() && end < word.length()) {
            word = word.subSequence(start, end);

            return new SelectedWord(left, right, word);
        }

        return null;
    }

    private static boolean isBoundaryCharacter(char c) {
        char[] boundaryChars = {' ', '.', ',', '\"', '\'', '\n', '\t', ':', '!', '\''};

        for (char boundaryChar : boundaryChars) {
            if (boundaryChar == c) {
                return true;
            }
        }

        return false;
    }

    public void navigateTo(String rawHref) {

        // this.prevIndex = this.getIndex();
        // this.prevPos = this.getProgressPosition();

        // URLDecode the href, so it does not contain %20 etc.
        String href = URLDecoder.decode(StringUtil.substringBefore(rawHref, Constants.FRAGMENT_SEPARATOR_CHAR));

        // Don't decode the anchor.
        String anchor = StringUtil.substringAfterLast(rawHref, Constants.FRAGMENT_SEPARATOR_CHAR);

        if (!"".equals(anchor)) {
            this.storedAnchor = anchor;
        }

        // Just an anchor and no href; resolve it on this page
        if (href.length() == 0) {
            restorePosition();
        } else {

            this.strategy.clearText();
            this.strategy.setPosition(0);

            if (this.spine.navigateByHref(href)) {
                loadText();
            } else {

                Resource resource = book.getResources().getByHref(href);

                if (resource != null) {
                    loadText(resource);
                } else {
                    loadText(new SpannedString(getContext().getString(R.string.dead_link)));
                }
            }
        }
    }

    /**
     * 导航到具体的一页
     */
    public void navigateToPage(int pageIndex) {
        if (this.spine != null && this.spine.getPageOffsets() != null && this.spine.getPageOffsets().size() > 0) {
            // 找到Index和Position
            int index = -1;
            int position = -1;
            int pageCount = 0;
            for (int i = 0; i < this.spine.getPageOffsets().size(); i++) {
                index = i;
                for (int j = 0; j < this.spine.getPageOffsets().get(i).size(); j++) {
                    if (pageCount == pageIndex) {
                        position = this.spine.getPageOffsets().get(i).get(j);
                        break;
                    }
                    pageCount++;
                }
                if (position >= 0) {
                    // 找到了
                    break;
                }
            }
            if (index >= 0 && position >= 0) {
                // 跳转
                navigateTo(index, position);
            }
        }
    }

    /**
     * 导航到具体的百分比
     *
     * @param percentage 0-100
     */
    public void navigateToPercentage(int percentage) {

        if (spine == null) {
            return;
        }

        int index = 0;

        if (percentage > 0) {

            double targetPoint = (double) percentage / 100d;
            List<Double> percentages = this.spine.getRelativeSizes();

            if (percentages == null || percentages.isEmpty()) {
                return;
            }

            double total = 0;

            for (; total < targetPoint && index < percentages.size(); index++) {
                total = total + percentages.get(index);
            }

            index--;

            // Work-around for when we get multiple events.
            if (index < 0 || index >= percentages.size()) {
                return;
            }

            double partBefore = total - percentages.get(index);
            double progressInPart = (targetPoint - partBefore) / percentages.get(index);

            this.strategy.setRelativePosition(progressInPart);
        } else {
            this.strategy.setPosition(0);
        }

        // this.prevPos = this.getProgressPosition();
        doNavigation(index);
    }

    /**
     * 根据搜索结果进行导航
     */
    public void navigateBySearchResult(SearchResult searchResult) {

        // this.prevPos = this.getProgressPosition();
        this.strategy.setPosition(searchResult.getStart());

        // this.prevIndex = this.getIndex();

        this.storedIndex = searchResult.getIndex();
        this.strategy.clearText();
        this.spine.navigateByIndex(searchResult.getIndex());

        loadText(searchResult.getQuery());
    }

    private void doNavigation(int index) {

        // Check if we're already in the right part of the book
        if (index == this.getIndex()) {
            restorePosition();
            progressUpdate();
            return;
        }
        // this.prevIndex = this.getIndex();

        this.storedIndex = index;
        this.strategy.clearText();
        this.spine.navigateByIndex(index);

        loadText();
    }

    public void navigateTo(int index, int position) {
        // this.prevPos = this.getProgressPosition();
        this.strategy.setPosition(position);

        doNavigation(index);
    }

    /**
     * 获取目录信息
     *
     * @return List<TocEntry>
     */
    public List<TocEntry> getTableOfContents() {
        if (this.book == null) {
            return null;
        }

        List<TocEntry> result = new ArrayList<>();

        flatten(book.getTableOfContents().getTocReferences(), result, 0);

        return result;
    }

    private void flatten(List<TOCReference> refs, List<TocEntry> entries, int level) {

        if (spine == null || refs == null || refs.isEmpty()) {
            return;
        }

        for (TOCReference ref : refs) {

            String title = "";

            for (int i = 0; i < level; i++) {
                title += "  ";
            }

            title += ref.getTitle();

            if (ref.getResource() != null) {
                entries.add(new TocEntry(title, spine.resolveTocHref(ref.getCompleteHref())));
            }

            flatten(ref.getChildren(), entries, level + 1);
        }
    }

    // @Override
    // public void fling(int velocityY) {
    // strategy.clearStoredPosition();
    // super.fling(velocityY);
    // }

    public int getIndex() {
        if (this.spine == null) {
            return storedIndex;
        }

        return this.spine.getPosition();
    }

    public int getStartOfCurrentPage() {
        return strategy.getTopLeftPosition();
    }

    public int getProgressPosition() {
        return strategy.getProgressPosition();
    }

    public void setPosition(int pos) {
        this.strategy.setPosition(pos);
    }

    public void update() {
        strategy.updateGUI();
    }

    /**
     * 获取当前页的首行字符，用于添加书签
     */
    public String getFirstLine() {
        int topLeft = strategy.getTopLeftPosition();

        String plainText = "";

        if (getStrategy().getText() != null) {
            plainText = getStrategy().getText().toString();
        }

        if (plainText.length() == 0) {
            return plainText;
        }

        plainText = plainText.substring(topLeft, plainText.length()).trim();

        int firstNewLine = plainText.indexOf('\n');

        if (firstNewLine == -1) {
            return plainText;
        }

        return plainText.substring(0, firstNewLine);
    }

    /**
     * Scrolls to a previously stored point.
     * <p>
     * Call this after setPosition() to actually go there.
     */
    private void restorePosition() {

        if (this.storedAnchor != null) {

            Integer anchorValue = this.textLoader.getAnchor(spine.getCurrentHref(), storedAnchor);

            if (anchorValue != null) {
                strategy.setPosition(anchorValue);
                this.storedAnchor = null;
            }
        }

        this.strategy.updatePosition();
    }

    private void setImageSpan(SpannableStringBuilder builder, Drawable drawable, int start, int end) {
        builder.setSpan(new ImageSpan(drawable), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

        if (spine != null && spine.isCover()) {
            builder.setSpan(new CenterSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
    }

    private void setCustomImageSpan(SpannableStringBuilder builder, String path, Drawable drawable, int start, int end) {
        builder.setSpan(cacheImage(path, drawable), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    }

    /**
     * 缓存图片
     */
    private CustomImageSpan cacheImage(String path, Drawable drawable) {
        final String savePath = EpubConst.EPUB_IMG_LOCAL_PATH + bookId + "/";
        FileUtil.checkDir(savePath);
        final String fullName = savePath + FileUtil.getFileName(path);
        //TODO 集成到企业书屋时，需要实现按书籍id分类缓存
        if (!FileUtil.isFileExists(fullName)) {
            FileUtil.saveBitmap(((FastBitmapDrawable) drawable).getBitmap(), savePath, FileUtil.getFileName(path));
        }
        return new CustomImageSpan(drawable, fullName);
    }

    private void setVideoImageSpan(SpannableStringBuilder builder, Drawable drawable, int start, int end,
                                   String videoHref) {

        VideoImageSpan vSpan = new VideoImageSpan(drawable);
        vSpan.setVideoHref(videoHref);

        builder.setSpan(vSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    }

    private void setAudioImageSpan(SpannableStringBuilder builder, Drawable drawable, int start, int end, String audioHref) {

        AudioImageSpan aSpan = new AudioImageSpan(drawable);
        aSpan.setAudioHref(audioHref);

        builder.setSpan(aSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    }

    private class ImageCallback implements ResourceCallback {

        private SpannableStringBuilder builder;
        private int start;
        private int end;
        private String audioId;

        private String storedHref;

        private boolean fakeImages;

        ImageCallback(String href, SpannableStringBuilder builder, int start, int end, boolean fakeImages,
                      String audioId) {
            this.builder = builder;
            this.start = start;
            this.end = end;
            this.storedHref = href;
            this.fakeImages = fakeImages;
            this.audioId = audioId;
        }

        @Override
        public void onLoadResource(String href, InputStream input) {

            if (fakeImages) {
                LOG.debug("Faking image for href: " + href);
                setFakeImage(input);
            } else {
                LOG.debug("Loading real image for href: " + href);
                setBitmapDrawable(input);
            }

        }

        private void setFakeImage(InputStream input) {
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;

            BitmapFactory.decodeStream(input, null, options);

            int[] sizes = calculateSize(options.outWidth, options.outHeight);

            ShapeDrawable draw = new ShapeDrawable(new RectShape());
            draw.setBounds(0, 0, sizes[0], sizes[1]);

            setImageSpan(builder, draw, start, end);
        }

        private void setBitmapDrawable(InputStream input) {
            Bitmap bitmap = null;
            try {
                bitmap = getBitmap(input);

                if (bitmap == null || bitmap.getHeight() < 1 || bitmap.getWidth() < 1) {
                    return;
                }

            } catch (OutOfMemoryError outofmem) {
                LOG.error("Could not load image", outofmem);
            }

            if (bitmap != null) {

                FastBitmapDrawable drawable = new FastBitmapDrawable(bitmap);

                drawable.setBounds(0, 0, bitmap.getWidth() - 1, bitmap.getHeight() - 1);
                if (audioId == null) {
                    setImageSpan(builder, drawable, start, end);
                } else if (audioId.equals(CustomImageSpan.TAG)) {
                    setCustomImageSpan(builder, EpubConst.EPUB_IMG_LOCAL_PATH + FileUtil.getFileName(storedHref), drawable, start, end);
                } else {
                    setAudioImageSpan(builder, drawable, start, end, audioId);
                }

                LOG.debug("Storing image in cache: " + storedHref);

                textLoader.storeImageInChache(storedHref, drawable);
            }
        }

        private Bitmap getBitmap(InputStream input) {
            if (Configuration.IS_NOOK_TOUCH) {
                // Workaround for skia failing to load larger (>8k?) JPEGs on
                // Nook Touch and maybe other older Eclair devices (unknown!)
                // seems the underlying problem is the ZipInputStream returning
                // data in chunks,
                // may be as per
                // http://code.google.com/p/android/issues/detail?id=6066
                // workaround is to stream the whole image out of the Zip to a
                // ByteArray, then pass that on to the bitmap decoder
                try {
                    input = new ByteArrayInputStream(IOUtil.toByteArray(input));
                } catch (IOException ex) {
                    LOG.error("Failed to extract full image from epub stream: " + ex.toString());
                }
            }

            Bitmap originalBitmap = BitmapFactory.decodeStream(input);

            if (originalBitmap != null) {
                int originalWidth = originalBitmap.getWidth();
                int originalHeight = originalBitmap.getHeight();

                int[] targetSizes = calculateSize(originalWidth, originalHeight);
                int targetWidth = targetSizes[0];
                int targetHeight = targetSizes[1];

                if (targetHeight != originalHeight || targetWidth != originalWidth) {
                    return Bitmap.createScaledBitmap(originalBitmap, targetWidth, targetHeight, true);
                }
            }

            return originalBitmap;
        }
    }

    private int[] calculateSize(int originalWidth, int originalHeight) {
        int[] result = new int[]{originalWidth, originalHeight};

        int screenHeight = getHeight() - (verticalMargin * 2);
        int screenWidth = getWidth() - (horizontalMargin * 2);

        // We scale to screen width for the cover or if the image is too
        // wide.
        if (originalWidth > screenWidth || originalHeight > screenHeight || spine.isCover()) {

            float ratio = (float) originalWidth / (float) originalHeight;

            int targetHeight = screenHeight - 1;
            int targetWidth = (int) (targetHeight * ratio);

            if (targetWidth > screenWidth - 1) {
                targetWidth = screenWidth - 1;
                targetHeight = (int) (targetWidth * (1 / ratio));
            }

            LOG.debug("Rescaling from " + originalWidth + "x" + originalHeight + " to " + targetWidth + "x"
                    + targetHeight);

            if (targetWidth > 0 || targetHeight > 0) {
                result[0] = targetWidth;
                result[1] = targetHeight;
            }
        }

        return result;
    }

    private class VideoImageCallback implements ResourceCallback {

        private SpannableStringBuilder builder;
        private int start;
        private int end;

        private String storedHref;

        private boolean fakeImages;

        VideoImageCallback(String href, SpannableStringBuilder builder, int start, int end, boolean fakeImages) {
            this.builder = builder;
            this.start = start;
            this.end = end;
            this.storedHref = href;
            this.fakeImages = fakeImages;
        }

        @Override
        public void onLoadResource(String href, InputStream input) {
            if (fakeImages) {
                LOG.debug("Faking image for href: " + href);
                setFakeImage(input);
            } else {
                LOG.debug("Loading real image for href: " + href);
                setBitmapDrawable(input);
            }
        }

        private void setFakeImage(InputStream input) {
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;

            // 读取视频的大小,形成缩略图，性能原因暂时不实现，以后加上后台更新策略，使用ThumbnailUtils类
            BitmapFactory.decodeResource(getResources(), R.drawable.epub3_play_video, options);

            int[] sizes = calculateSize(options.outWidth, options.outHeight);
            ShapeDrawable draw = new ShapeDrawable(new RectShape());
            draw.setBounds(0, 0, sizes[0], sizes[1]);
            setImageSpan(builder, draw, start, end);
        }

        private void setBitmapDrawable(InputStream input) {
            Bitmap bitmap = null;
            try {
                // TODO lc 获取一张静态视频播放图片
                bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.epub3_play_video);

                if (bitmap == null || bitmap.getHeight() < 1 || bitmap.getWidth() < 1) {
                    return;
                }

            } catch (OutOfMemoryError outofmem) {
                LOG.error("Could not load image", outofmem);
            }

            if (bitmap != null) {

                Bitmap aimBitmap = bitmap;

                int originalWidth = bitmap.getWidth();
                int originalHeight = bitmap.getHeight();

                int[] targetSizes = calculateSize(originalWidth, originalHeight);
                int targetWidth = targetSizes[0];
                int targetHeight = targetSizes[1];

                if (targetHeight != originalHeight || targetWidth != originalWidth) {
                    aimBitmap = Bitmap.createScaledBitmap(bitmap, targetWidth, targetHeight, true);
                }

                FastBitmapDrawable drawable = new FastBitmapDrawable(aimBitmap);

                drawable.setBounds(0, 0, aimBitmap.getWidth() - 1, aimBitmap.getHeight() - 1);
                setVideoImageSpan(builder, drawable, start, end, storedHref);

                LOG.debug("Storing videoimage in cache: " + storedHref);
                textLoader.storeImageInChache(storedHref, drawable);
            }
        }

    }

    /**
     * 视频处理Handler
     *
     * @author dave
     */
    private class VideoAudioTagHandler extends TagNodeHandler {

        private boolean fakeImages;

        VideoAudioTagHandler(boolean fakeImages) {
            this.fakeImages = fakeImages;
        }

        @TargetApi(Build.VERSION_CODES.FROYO)
        @Override
        public void handleTagNode(TagNode node, SpannableStringBuilder builder, int start, int end, SpanStack span) {

			/*
             * <source src="../Video/shared-culture.mp4" type="video/mp4" />
			 * </video>
			 */

            if (node.hasAttribute("type")) {
                if (node.getAttributeByName("type").equals("video/mp4")) {
                    // 处理视频资源
                    String src = node.getAttributeByName("src");

                    if (src == null) {
                        return;
                    }
                    // 占位符
                    builder.append(COMPLEX_OBJ_STRING_HOLDER);
                    String resolvedHref = spine.resolveHref(src);
                    if (textLoader.hasCachedImage(resolvedHref) && !fakeImages) {
                        // 该图像已经被生成并缓存了
                        Drawable drawable = textLoader.getCachedImage(resolvedHref);
                        setVideoImageSpan(builder, drawable, start, builder.length(), resolvedHref);
                        LOG.debug("Got cached href: " + resolvedHref);
                    } else {

                        if (fakeImages) {
                            BitmapFactory.Options options = new BitmapFactory.Options();
                            options.inJustDecodeBounds = true;
                            BitmapFactory.decodeResource(getResources(), R.drawable.epub3_play_video, options);
                            int[] sizes = calculateSize(options.outWidth, options.outHeight);
                            ShapeDrawable draw = new ShapeDrawable(new RectShape());
                            draw.setBounds(0, 0, sizes[0], sizes[1]);
                            setImageSpan(builder, draw, start, builder.length());
                        } else {
                            // 进行资源加载，加载完成后对Span进行操作
                            LOG.debug("Loading href: " + resolvedHref);
                            this.registerCallback(resolvedHref, new VideoImageCallback(resolvedHref, builder, start,
                                    builder.length(), fakeImages));
                        }
                    }
                }
            }

        }

        void registerCallback(String resolvedHref, VideoImageCallback callback) {
            BookView.this.loader.registerCallback(resolvedHref, callback);
        }

    }

    /**
     * 图像处理Handler
     *
     * @author dave
     */
    private class ImageTagHandler extends TagNodeHandler {

        private boolean fakeImages;

        ImageTagHandler(boolean fakeImages) {
            this.fakeImages = fakeImages;
        }

        @TargetApi(Build.VERSION_CODES.FROYO)
        @Override
        public void handleTagNode(TagNode node, SpannableStringBuilder builder, int start, int end, SpanStack span) {

            // 判断父节点是否是small和data-audioRef="true"，如果是则不处理，因为已经在small tag中处理了
            TagNode parent = node.getParent();
            if (parent.getName().equals("small") && parent.hasAttribute("data-audioRef")
                    && parent.getAttributeByName("data-audioRef").equals("true")) {
                return;
            }

            String src = node.getAttributeByName("src");

            if (src == null) {
                src = node.getAttributeByName("href");
            }

            if (src == null) {
                src = node.getAttributeByName("xlink:href");
            }

            if (src == null) {
                return;
            }

            if (!TextUtils.isEmpty(src) && src.startsWith("OEBPS/")) {
                src = src.replace("OEBPS", "..");
            }

            // 占位符，在最终显示的时候用图片进行替换[OBJ]
            builder.append(COMPLEX_OBJ_STRING_HOLDER);

            if (src.startsWith("data:image")) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {

                    try {
                        String dataString = src.substring(src.indexOf(',') + 1);

                        byte[] binData = Base64.decode(dataString, Base64.DEFAULT);

                        setCustomImageSpan(
                                builder, src,
                                new BitmapDrawable(getContext().getResources(), BitmapFactory.decodeByteArray(binData,
                                        0, binData.length)), start, builder.length());
                    } catch (OutOfMemoryError | IllegalArgumentException outOfMemoryError) {
                        // Simply don't load
                    }

                }

            } else if (spine != null) {

                String resolvedHref = spine.resolveHref(src);

                if (textLoader.hasCachedImage(resolvedHref) && !fakeImages) {
                    Drawable drawable = textLoader.getCachedImage(resolvedHref);
                    setCustomImageSpan(builder, src, drawable, start, builder.length());
                    LOG.debug("Got cached href: " + resolvedHref);
                } else {
                    LOG.debug("Loading href: " + resolvedHref);
                    this.registerCallback(resolvedHref,
                            new ImageCallback(resolvedHref, builder, start, builder.length(), fakeImages, CustomImageSpan.TAG));
                }
            }
        }

        protected void registerCallback(String resolvedHref, ImageCallback callback) {
            BookView.this.loader.registerCallback(resolvedHref, callback);
        }
    }

    /**
     * SmallTag，播放音频的占位符tag
     *
     * @author dave
     */
    private class SmallTagHandler extends TagNodeHandler {
        private boolean fakeImages;

        SmallTagHandler(boolean fakeImages) {
            this.fakeImages = fakeImages;
        }

        @TargetApi(Build.VERSION_CODES.FROYO)
        @Override
        public void handleTagNode(TagNode node, SpannableStringBuilder builder, int start, int end, SpanStack span) {

            // data-audioRef="true"
            if (node.hasAttribute("data-audioRef") && node.hasAttribute("href")) {
                TagNode[] cNodes = node.getAllElements(false);
                if (cNodes.length == 1) {
                    TagNode imgNode = cNodes[0];
                    String audioId = node.getAttributeByName("href").substring(1);

                    // 处理音频播放图像
                    String src = imgNode.getAttributeByName("src");

                    if (src == null) {
                        src = imgNode.getAttributeByName("href");
                    }

                    if (src == null) {
                        src = imgNode.getAttributeByName("xlink:href");
                    }

                    if (src == null) {
                        return;
                    }
                    builder.append(COMPLEX_OBJ_STRING_HOLDER);
                    if (spine != null) {

                        String resolvedHref = spine.resolveHref(src);

                        if (textLoader.hasCachedImage(resolvedHref) && !fakeImages) {
                            Drawable drawable = textLoader.getCachedImage(resolvedHref);
                            setAudioImageSpan(builder, drawable, start, builder.length(), audioId);
                            LOG.debug("Got cached href: " + resolvedHref);
                        } else {
                            LOG.debug("Loading href: " + resolvedHref);
                            registerCallback(resolvedHref, new ImageCallback(resolvedHref,
                                    builder, start, builder.length(), fakeImages, audioId));
                        }
                    }
                }

            }

        }

        protected void registerCallback(String resolvedHref, ImageCallback callback) {
            BookView.this.loader.registerCallback(resolvedHref, callback);
        }
    }

    @Override
    public void setBackgroundColor(int color) {
        super.setBackgroundColor(color);
        if (this.childView != null) {
            this.childView.setBackgroundColor(color);
        }
    }

    /**
     * 点击高量区域
     */
    public void highlightClicked(HighLight highLight) {
        for (BookViewListener listener : listeners) {
            listener.onHighLightClick(highLight);
        }
    }

    /**
     * 电视视频播放图片，进行视频播放
     */
    public void videoClicked(final String videoHref) {
        // TODO lc 打开视频播放器
        Log.d("BookView", "videoClicked:" + videoHref);
        Toast.makeText(getContext(), "正在打开视频，请稍候", Toast.LENGTH_LONG).show();

        Thread openThread = new Thread(new Runnable() {

            @Override
            public void run() {
                Resource r = book.getResources().getByHref(videoHref);
                try {

                    InputStream in = r.getInputStream();
                    byte[] bytes = new byte[(int) r.getSize()];
                    int readCount = 0; // 已经成功读取的字节的个数
                    while (readCount < r.getSize()) {
                        readCount += in.read(bytes, readCount, (int) (r.getSize() - readCount));
                    }
                    // 保存文件到本地
                    final String tempVideoPath = EpubConst.EPUB_TEMP_LOCAL_PATH + "temp.mp4";
                    if (FileUtil.outPutToFile(bytes, tempVideoPath)) {
                        ((Activity) getContext()).runOnUiThread(new Runnable() {

                            @Override
                            public void run() {
                                Intent i = new Intent(getContext(), VideoPlayActivity.class);
                                i.putExtra("tempVideoPath", tempVideoPath);
                                getContext().startActivity(i);
                            }
                        });
                    }
                } catch (OutOfMemoryError e) {
                    e.printStackTrace();
                } catch (Exception e) {
                    Toast.makeText(getContext(), "发生未知错误", Toast.LENGTH_LONG).show();
                    e.printStackTrace();
                }

            }
        });

        openThread.start();

    }

    MediaPlayer audioPlayer;

    public boolean isAudioPlaying() {
        try {
            if (audioPlayer != null) {
                return audioPlayer.isPlaying();
            }
        } catch (Exception ignored) {
        }
        return false;
    }

    public void pauseAudio() {
        try {
            if (audioPlayer != null && audioPlayer.isPlaying()) {
                audioPlayer.pause();
            }
        } catch (IllegalStateException e) {
            e.printStackTrace();
        }
    }

    public void startAudio() {

        try {
            if (audioPlayer != null && !audioPlayer.isPlaying()) {
                audioPlayer.start();
            }
        } catch (IllegalStateException e) {
            e.printStackTrace();
        }
    }

    public void stopAudio() {

        try {
            if (audioPlayer != null) {
                audioPlayer.release();
            }
        } catch (IllegalStateException e) {
            e.printStackTrace();
        }
    }

    /**
     * 点击到音频播放图片，播放音频
     */
    public void audioImageClicked(final String audioId) {

        HashMap<String, String> currAudioResources;
        ChapterSpan cacheText = textLoader.getCachedTextForResource(spine.getCurrentResource());
        if (cacheText != null) {
            currAudioResources = cacheText.getAudioResources();
            if (currAudioResources.containsKey(audioId)) {

                if (audioPlayer != null) {
                    audioPlayer.release();
                }
                audioPlayer = new MediaPlayer();
                audioPlayer.setOnCompletionListener(new OnCompletionListener() {

                    @Override
                    public void onCompletion(MediaPlayer mp) {
                        mp.release();
                        // 通知工具栏隐藏播放工具条
                        if (listeners != null && listeners.size() > 0) {
                            for (BookViewListener lis : listeners) {
                                lis.onAudioPlayComplete();
                            }
                        }
                    }
                });

                // 播放音频
                Resource r = book.getResources().getByHref(this.spine.resolveHref(currAudioResources.get(audioId)));
                String aimPath = EpubConst.EPUB_TEMP_LOCAL_PATH + "temp.mp3";
//				String aimPath = Environment.getExternalStorageDirectory() + "/tyyd_epub/tempVideo/temp.mp3";
                if (r != null) {
                    try {
                        InputStream in = r.getInputStream();
                        byte[] bytes = new byte[(int) r.getSize()];
                        int readCount = 0; // 已经成功读取的字节的个数
                        while (readCount < r.getSize()) {
                            readCount += in.read(bytes, readCount, (int) (r.getSize() - readCount));
                        }
                        // 保存文件
                        if (FileUtil.outPutToFile(bytes, aimPath)) {
                            if (audioPlayer.isPlaying()) {
                                audioPlayer.stop();
                            }
                            audioPlayer.setDataSource(aimPath);
                            audioPlayer.prepare();
                            audioPlayer.start();

                            // 通知工具栏显示播放工具条
                            if (listeners != null && listeners.size() > 0) {
                                for (BookViewListener lis : listeners) {
                                    lis.onAudioPlayStart();
                                }
                            }
                        }
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        }

    }

    /**
     * 点击到注释，显示Note
     */
    public void noteImageClicked(String noteId) {
        // 查找NoteId,显示Note
        HashMap<String, Spannable> currNoteResources;
        ChapterSpan cacheText = textLoader.getCachedTextForResource(spine.getCurrentResource());
        if (cacheText != null) {
            currNoteResources = cacheText.getNoteResources();
            String key = this.spine.getCurrentResource().getHref() + noteId;
            if (currNoteResources.containsKey(key)) {
                Toast.makeText(getContext(), currNoteResources.get(key), Toast.LENGTH_LONG).show();
            } else {
                Toast.makeText(getContext(), "没有找到相关注释", Toast.LENGTH_SHORT).show();
            }
        }

    }

    /**
     * 点击图片处理
     */
//	public void imageClicked(Drawable drawable) {
//		Toast.makeText(getContext(), "imageClicked", Toast.LENGTH_SHORT).show();
//		Log.e("QY", "image click!");
//	}
    public void imageClicked(String imageHref) {
        if (!TextUtils.isEmpty(imageHref)) {
            Intent intent = new Intent(getContext(), PictureActivity.class);
            intent.putExtra(PictureActivity.PATH_KEY, imageHref);
            if (null != configuration) {
                intent.putExtra(PictureActivity.COLOR_KEY, configuration.getBackgroundColor());
            }
            getContext().startActivity(intent);
        } else {
            System.err.println("点击的图片路径为空");
//			Toast.makeText(getContext(), "ImageSpan 图片路径为空", Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * 设置字体颜色
     */
    public void setTextColor(int color) {
        if (this.childView != null) {
            this.childView.setTextColor(color);
        }

        this.tableHandler.setTextColor(color);
    }

    public Book getBook() {
        return book;
    }

    /**
     * 设置字体大小
     */
    public void setTextSize(float textSize) {
        this.childView.setTextSize(textSize);
        this.tableHandler.setTextSize(textSize);
    }

    public void addListener(BookViewListener listener) {
        this.listeners.add(listener);
    }

    /**
     * 通知文件打开完成
     */
    private void bookOpened(Book book) {
        for (BookViewListener listener : this.listeners) {
            listener.bookOpened(book);
        }
    }

    private void errorOnBookOpening(String errorMessage) {
        for (BookViewListener listener : this.listeners) {
            listener.errorOnBookOpening(errorMessage);
        }
    }

    private void parseEntryStart(int entry) {
        for (BookViewListener listener : this.listeners) {
            listener.parseEntryStart(entry);
        }
    }

    private void parseEntryComplete(String name) {
        for (BookViewListener listener : this.listeners) {
            listener.parseEntryComplete(name);
        }
    }

    /**
     * 通知正在打开文件
     */
    private void fireOpenFile() {
        for (BookViewListener listener : this.listeners) {
            listener.readingFile();
        }
    }

    /**
     * 通知正在展示文字
     */
    private void fireRenderingText() {
        for (BookViewListener listener : this.listeners) {
            listener.renderingText();
        }
    }

    /**
     * 获取百分比信息
     */
    public int getPercentageFor(int index, int offset) {
        if (spine != null) {
            return spine.getProgressPercentage(index, offset);
        }
        return -1;
    }

    /**
     * 更新进度
     */
    private void progressUpdate() {
        if (spine != null && strategy.getText() != null && strategy.getText().length() > 0) {
            double progressInPart = (double) getProgressPosition() / (double) strategy.getText().length();
            if (strategy.getText().length() > 0 && strategy.isAtEnd()) {
                progressInPart = 1d;
            }
            int progress = spine.getProgressPercentage(progressInPart);
            if (progress != -1) {
                int pageNumber = getPageNumberFor(getIndex(), getProgressPosition());
                for (BookViewListener listener : listeners) {
                    listener.progressUpdate(progress, pageNumber, spine.getTotalNumberOfPages());
                }
            }
        }
    }

    /**
     * 获取总页数
     */
    public int getTotalNumberOfPages() {
        if (spine != null) {
            return spine.getTotalNumberOfPages();
        }
        return -1;
    }

    /**
     * 获取页码
     *
     * @param index    章节index
     * @param position 章节内的位移
     * @return int
     */
    public int getPageNumberFor(int index, int position) {

        if (spine == null) {
            return -1;
        }

        LOG.debug("Looking for pageNumber for index=" + index + ", position=" + position);

        int pageNum = -1;

        List<List<Integer>> pageOffsets = spine.getPageOffsets();

        if (pageOffsets == null || index >= pageOffsets.size()) {
            return -1;
        }

        for (int i = 0; i < index; i++) {

            int pages = pageOffsets.get(i).size();

            pageNum += pages;

            LOG.debug("Index " + i + ": pages=" + pages);
        }

        List<Integer> offsets = pageOffsets.get(index);

        LOG.debug("Pages before this index: " + pageNum);

        LOG.debug("Offsets according to spine: " + asString(offsets));

        if (this.strategy instanceof FixedPagesStrategy) {
            List<Integer> strategyOffsets = ((FixedPagesStrategy) this.strategy).getPageOffsets();
            LOG.debug("Offsets according to strategy: " + asString(strategyOffsets));
        }

        for (int i = 0; i < offsets.size() && offsets.get(i) <= position; i++) {
            pageNum++;
        }

        LOG.debug("Calculated pageNumber=" + pageNum);
        return pageNum;
    }

    private static String asString(List<Integer> offsets) {

        StringBuilder stringBuilder = new StringBuilder("[ ");

        for (int i = 0; i < offsets.size(); i++) {
            stringBuilder.append(offsets.get(i)).append(" ");
        }

        stringBuilder.append(" ]");

        return stringBuilder.toString();
    }

    /**
     * 是否需要分页
     *
     * @return boolean
     */
    private boolean needsPageNumberCalculation() {

        if (spine != null) {
            return spine.getPageOffsets() == null || spine.getPageOffsets().size() == 0;
        }

        List<List<Integer>> offsets = configuration.getPageOffsets(fileName);

        // Check if we need to calculate at all, if not exit.
        return !(offsets != null && offsets.size() > 0);

    }

    public void setEnableScrolling(boolean enableScrolling) {

        if (this.strategy == null || this.strategy.isScrolling() != enableScrolling) {
            int pos = -1;
            boolean wasNull = true;
            Spanned text = null;
            if (this.strategy != null) {
                pos = this.strategy.getTopLeftPosition();
                text = this.strategy.getText();
                this.strategy.clearText();
                wasNull = false;
            }
            // if (enableScrolling) {
            // this.strategy = scrollingStrategyProvider.get();
            // } else {
            this.strategy = fixedPagesStrategyProvider.get();
            // }

            strategy.setBookView(this);

            if (!wasNull) {
                this.strategy.setPosition(pos);
            }
            if (text != null && text.length() > 0) {
                this.strategy.loadText(text);
            }
        }
    }

    public static class InnerView extends TextView {

        private BookView bookView;

        private long blockUntil = 0L;

        public InnerView(Context context, AttributeSet attributes) {
            super(context, attributes);
        }

        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            bookView.onInnerViewResize();
        }

        @Override
        public void onWindowFocusChanged(boolean hasWindowFocus) {
			/*
			 * We override this method to do nothing, since the base
			 * implementation closes the ActionMode.
			 * 
			 * This means that when the user clicks the overflow menu, the
			 * ActionMode is stopped and text selection is ended.
			 */
        }

        public void setBookView(BookView bookView) {
            this.bookView = bookView;
        }

        public void setBlockUntil(long blockUntil) {
            this.blockUntil = blockUntil;
        }

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
        }

        @TargetApi(Build.VERSION_CODES.HONEYCOMB)
        @Override
        public ActionMode startActionMode(ActionMode.Callback callback) {

            if (System.currentTimeMillis() > blockUntil) {

                LOG.debug("InnerView starting action-mode");
                return super.startActionMode(callback);

            } else {
                LOG.debug("Not starting action-mode yet, since block time hasn't expired.");
                clearFocus();
                return null;
            }

        }
    }

    private enum BookReadPhase {
        START, OPEN_FILE, PARSE_TEXT, DONE
    }

    private static class SearchResultSpan extends BackgroundColorSpan {
        SearchResultSpan() {
            super(Color.YELLOW);
        }
    }

    /**
     * 载入epub文件Task
     *
     * @author Dave
     */
    private class OpenFileTask extends QueueableAsyncTask<Void, BookReadPhase, Book> {

        private String error;

        @Override
        protected void onPreExecute() {
            fireOpenFile();
        }

        @Override
        protected Book doInBackground(Void... args) {
            try {
                return initBookAndSpine();
            } catch (IOException io) {
                this.error = io.getLocalizedMessage();
            } catch (OutOfMemoryError e) {
                this.error = getContext().getString(R.string.out_of_memory);
            }

            return null;
        }

        @Override
        protected void doOnPostExecute(Book book) {

            if (book != null) {
                bookOpened(book);
            } else {
                errorOnBookOpening(this.error);
            }
        }
    }

    private class PreLoadTask extends QueueableAsyncTask<Void, Void, Void> {

        @Override
        protected Void doInBackground(Void... voids) {

            if (spine == null) {
                return null;
            }

            Resource resource = spine.getNextResource();
            if (resource == null) {
                return null;
            }

            ChapterSpan cachedText = textLoader.getCachedTextForResource(resource);

            if (cachedText == null) {
                try {
                    cachedText = textLoader.getText(resource, new QueueableAsyncTaskCancellationCallback(this));
                    loader.load();
                } catch (Exception | OutOfMemoryError e) {
                    // Ignore
                }
            }
            return null;
        }
    }

    /**
     * 载入文字
     *
     * @author Dave
     */
    private class LoadTextTask extends QueueableAsyncTask<Resource, BookReadPhase, ChapterSpan> {

        private String name;

        private boolean wasBookLoaded;

        private String error;

        private String searchTerm = null;

        public LoadTextTask() {

        }

        LoadTextTask(String searchTerm) {
            this.searchTerm = searchTerm;
        }

        @Override
        protected void onPreExecute() {
            this.wasBookLoaded = book != null;
        }

        protected ChapterSpan doInBackground(Resource... resources) {
            publishProgress(BookReadPhase.START);
            if (loader != null) {
                loader.clear();
            }
            try {
                // this.name = spine.getCurrentTitle();
                Resource resource;
                if (resources == null || resources.length == 0) {
                    resource = spine.getCurrentResource();
                } else {
                    resource = resources[0];
                }
                publishProgress(BookReadPhase.PARSE_TEXT);
                ChapterSpan result = textLoader.getText(resource, new QueueableAsyncTaskCancellationCallback(this));
                loader.load(); // Load all image resources.
                // Clear any old highlighting spans
                Spannable spanResult = result.getSpannable();
                SearchResultSpan[] spans = spanResult.getSpans(0, spanResult.length(), SearchResultSpan.class);
                for (BackgroundColorSpan span : spans) {
                    spanResult.removeSpan(span);
                }

                // Highlight search results (if any)
                if (searchTerm != null) {
                    Pattern pattern = Pattern.compile(Pattern.quote((searchTerm)), Pattern.CASE_INSENSITIVE);
                    Matcher matcher = pattern.matcher(spanResult);
                    while (matcher.find()) {
                        spanResult.setSpan(new SearchResultSpan(), matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    }
                }

                // If the view isn't ready yet, wait a bit.
                while (getInnerView().getWidth() == 0) {
                    Thread.sleep(100);
                }

                strategy.loadText(spanResult);

                return result;
            } catch (Exception io) {
                LOG.error("Error loading text", io);
                this.error = String.format(getContext().getString(R.string.could_not_load), io.getMessage());
                this.wasBookLoaded = false;

            } catch (OutOfMemoryError io) {
                LOG.error("Error loading text", io);
                this.error = getContext().getString(R.string.out_of_memory);
                this.wasBookLoaded = false;
            }

            return null;
        }

        @Override
        protected void onProgressUpdate(BookReadPhase... values) {

            BookReadPhase phase = values[0];
            // huxudong:START & PARSE_TEXT don't show ProgressDialog
            switch (phase) {
                case START:
                    parseEntryStart(getIndex());
                    break;
                case PARSE_TEXT:
                    fireRenderingText();
                    break;
                case DONE:
                    parseEntryComplete(this.name);
                    break;
            }
        }

        @Override
        protected void doOnPostExecute(ChapterSpan result) {

            restorePosition();
            strategy.updateGUI();
            progressUpdate();

            onProgressUpdate(BookReadPhase.DONE);
            setCoverChapterView(result);

            for (BookViewListener listener : listeners) {
                listener.chapterUpdate(spine.getPosition(), result.getCoverPageInfo().getChapterTitle());
            }

            /**
             * This is a hack for scrolling not updating to the right position
             * on Android 4+
             */
            if (strategy.isScrolling()) {
                scrollHandler.postDelayed(new Runnable() {

                    @Override
                    public void run() {
                        restorePosition();
                    }
                }, 100);
            }
        }
    }

    private class CalculatePageNumbersTask extends QueueableAsyncTask<Object, Void, List<List<Integer>>> {

        @Override
        protected void onPreExecute() {
            for (BookViewListener listener : listeners) {
                listener.onStartCalculatePageNumbers();
            }
        }

        @Override
        protected List<List<Integer>> doInBackground(Object... params) {

            if (!needsPageNumberCalculation()) {
                return null;
            }

            try {
                List<List<Integer>> offsets = getOffsets();

                if (offsets != null) {
                    configuration.setPageOffsets(fileName, offsets);
                }

                LOG.debug("Calculated offsets: " + offsets);
                return offsets;

            } catch (OutOfMemoryError | Exception mem) {
                LOG.error("Could not read pagenumers", mem);
            }

            return null;
        }

        private void checkForCancellation() {
            if (isCancelRequested()) {
//				throw new IllegalStateException("Cancel requested");
                LOG.error("Cancel requested");
            }
        }

        /**
         * 用最少的资源加载整本书的文本偏移量。
         *
         * @throws IOException
         */
        private List<List<Integer>> getOffsets() throws IOException {

            List<List<Integer>> result = new ArrayList<>();
            final ResourceLoader imageLoader = new ResourceLoader(fileName);
            final ResourceLoader textResourceLoader = new ResourceLoader(fileName);

            // Image loader which only loads image dimensions
            ImageTagHandler tagHandler = new ImageTagHandler(true) {
                protected void registerCallback(String resolvedHref, ImageCallback callback) {
                    imageLoader.registerCallback(resolvedHref, callback);
                }
            };

            // Video loader
            VideoAudioTagHandler vTagHandler = new VideoAudioTagHandler(true);

            // Audio loader
            SmallTagHandler aTagHandler = new SmallTagHandler(true) {
                protected void registerCallback(String resolvedHref, ImageCallback callback) {
                    imageLoader.registerCallback(resolvedHref, callback);
                }
            };

            AudioTagHandler audioHandler = textLoader.new AudioTagHandler(true);
            AsideTagHandler asideHandler = textLoader.new AsideTagHandler(true);

            // Private spanner
            final HtmlSpanner mySpanner = new HtmlSpanner();

            mySpanner.setAllowStyling(configuration.isAllowStyling());
            mySpanner.setFontResolver(fontResolver);

            mySpanner.registerHandler("table", tableHandler);
            mySpanner.registerHandler("img", tagHandler);
            mySpanner.registerHandler("image", tagHandler);
            mySpanner.registerHandler("link", new CSSLinkHandler(textLoader));
            mySpanner.registerHandler("source", vTagHandler);
            mySpanner.registerHandler("small", aTagHandler);
            mySpanner.registerHandler("audio", audioHandler);
            mySpanner.registerHandler("aside", asideHandler);

            final Map<String, List<Integer>> offsetsPerSection = new HashMap<>();

            // We use the ResourceLoader here to load all the text in the book
            // in 1 pass,
            // but we only keep a single section in memory at each moment
            ResourceCallback callback = new ResourceCallback() {

                @Override
                public void onLoadResource(String href, InputStream stream) {
                    try {
                        checkForCancellation();
                        LOG.debug("CalculatePageNumbersTask: loading text for: " + href);
                        InputStream input = new ByteArrayInputStream(IOUtil.toByteArray(stream));

                        checkForCancellation();
                        Spannable text = mySpanner.fromHtml(input, false,
                                new QueueableAsyncTaskCancellationCallback(CalculatePageNumbersTask.this))
                                .getSpannable();

                        checkForCancellation();
                        imageLoader.load();

                        checkForCancellation();
                        FixedPagesStrategy fixedPagesStrategy = getFixedPagesStrategy();
                        fixedPagesStrategy.setBookView(BookView.this);

                        offsetsPerSection.put(href, fixedPagesStrategy.getPageOffsets(text, true));

                    } catch (IOException io) {
                        LOG.error("CalculatePageNumbersTask: failed to load text for " + href, io);
                    }
                }

            };

            // Do first pass: grab either cached text, or schedule a callback
            for (int i = 0; i < spine.size(); i++) {

                checkForCancellation();
                Resource res = spine.getResourceForIndex(i);

                ChapterSpan cachedText = textLoader.getCachedTextForResource(res);

                if (cachedText != null) {
                    LOG.debug("CalculatePageNumbersTask: Got cached text for href: " + res.getHref());

                    FixedPagesStrategy fixedPagesStrategy = getFixedPagesStrategy();
                    fixedPagesStrategy.setBookView(BookView.this);

                    offsetsPerSection.put(res.getHref(),
                            fixedPagesStrategy.getPageOffsets(cachedText.getSpannable(), false));

                } else {
                    LOG.debug("CalculatePageNumbersTask: Registering callback for href: " + res.getHref());
                    textResourceLoader.registerCallback(res.getHref(), callback);
                }
            }

            // Load callbacks, this will fill renderedText
            textResourceLoader.load();
            imageLoader.load();

            // Do a second pass and order the offsets correctly
            for (int i = 0; i < spine.size(); i++) {

                checkForCancellation();

                Resource res = spine.getResourceForIndex(i);

                List<Integer> offsets = null;

                // Scan for the full href
                for (String href : offsetsPerSection.keySet()) {
                    if (href.endsWith(res.getHref())) {
                        offsets = offsetsPerSection.get(href);
                        break;
                    }
                }
                if (offsets == null) {
                    LOG.error("CalculatePageNumbersTask: Missing text for href " + res.getHref());
                    return null;
                }
                result.add(offsets);
            }
            return result;
        }

        // Injection doesn't work from inner classes, so we construct it
        // ourselves.
        private FixedPagesStrategy getFixedPagesStrategy() {
            FixedPagesStrategy fixedPagesStrategy = new FixedPagesStrategy();
            fixedPagesStrategy.setConfig(configuration);
            fixedPagesStrategy.setLayoutFactory(new StaticLayoutFactory());
            return fixedPagesStrategy;
        }

        @Override
        public void doOnCancelled(List<List<Integer>> lists) {
            if (taskQueue.isEmpty()) {
                for (BookViewListener listener : listeners) {
                    listener.onCalculatePageNumbersComplete();
                }
            }
        }

        @Override
        protected void doOnPostExecute(List<List<Integer>> result) {

            LOG.debug("Pagenumber calculation completed.");

            for (BookViewListener listener : listeners) {
                listener.onCalculatePageNumbersComplete();
            }

            if (result != null && result.size() > 0) {
                spine.setPageOffsets(result);
                progressUpdate();
            }
        }
    }

    /**
     * 获取当前位移信息，相对于本章节
     */
    public int getCurrPageOffsets() {
        return this.strategy.getCurrentPosition();
    }

    public static class SelectedWord {
        private int startOffset;
        private int endOffset;
        private CharSequence text;

        public SelectedWord(int startOffset, int endOffset, CharSequence text) {
            this.startOffset = startOffset;
            this.endOffset = endOffset;
            this.text = text;
        }

        public int getStartOffset() {
            return startOffset;
        }

        public int getEndOffset() {
            return endOffset;
        }

        public CharSequence getText() {
            if (text == null) {
                return "";
            }
            return text;
        }
    }
}
